﻿using System;
using System.Reflection;

namespace Rules
{
    /// <summary>
    /// Provides methods for validating and accessing a property of an object reflectively.
    /// </summary>
    public class PropertyHelper<T>
    {
        public PropertyHelper(string propertyName)
        {
            ValidateNotNull(propertyName, "propertyName");
            DeclaringType = typeof(T);
            PropertyName = propertyName;
            Property = DeclaringType.GetProperty(propertyName);
        }

        private Type DeclaringType { get; set; }
        private string PropertyName { get; set; }
        private PropertyInfo Property { get; set; }

        /// <summary>
        /// Validates that the property exists and has a publically accessible getter.
        /// </summary>
        public void ValidateProperty()
        {
            ValidatePropertyExists();
            ValidatePropertyHasGetter();
            ValidatePropertyHasPublicGetter();
        }

        private void ValidateNotNull(object toValidate, string paramName)
        {
            if (toValidate == null || string.Empty.Equals(toValidate))
                throw new ArgumentNullException(paramName);
        }

        private void ValidatePropertyExists()
        {
            if (Property == null)
            {
                string message = string.Format("A public property named '{0}' could not be found on type '{1}'", PropertyName, DeclaringType.Name);
                throw new ApplicationException(message);
            }
        }

        private void ValidatePropertyHasGetter()
        {
            if (!Property.CanRead)
            {
                string message = string.Format("The property named '{0}' on type '{1}' does not have a get accessor", PropertyName, DeclaringType.Name);
                throw new ApplicationException(message);
            }
        }

        private void ValidatePropertyHasPublicGetter()
        {
            if (Property.GetGetMethod() == null)
            {
                string message = string.Format("The property named '{0}' on type '{1}' does not have a public get accessor", PropertyName, DeclaringType.Name);
                throw new ApplicationException(message);
            }
        }

        /// <summary>
        /// Retrieves the value of the property for the supplied instance of the declaring type.
        /// </summary>
        public object GetPropertyValue(T getFrom)
        {
            return Property.GetValue(getFrom, null);
        }
    }
}
